#ifndef _TLIB_h
#define _TLIB_h

#include <windows.h>
#include <commctrl.h>
#include <shellapi.h>

#include <map>

/*!
	The version of C++ standard that is being used.
	The C++17 standard.
*/
#ifndef MY_HAS_CPP17
#	ifdef _MSVC_LANG
#		if _MSVC_LANG > 201402L
#			define MY_HAS_CPP17	1
#		else /* _MSVC_LANG > 201402L */
#			define MY_HAS_CPP17	0
#		endif /* _MSVC_LANG > 201402L */
#	else /* _MSVC_LANG */
#		if __cplusplus > 201402L
#			define MY_HAS_CPP17	1
#		else /* __cplusplus > 201402L */
#			define MY_HAS_CPP17	0
#		endif /* __cplusplus > 201402L */
	#endif /* _MSVC_LANG */
#endif /* MY_HAS_CPP17 */
 
/*!
	The version of C++ standard that is being used.
	The C++14 standard.
*/
#ifndef MY_HAS_CPP14
#	ifdef _MSVC_LANG
#		if _MSVC_LANG > 201103L
#			define MY_HAS_CPP14	1
#		else /* _MSVC_LANG > 201103L */
#			define MY_HAS_CPP14	0
#		endif /* _MSVC_LANG > 201103L */
#	else /* _MSVC_LANG */
#		if __cplusplus > 201103L
#			define MY_HAS_CPP14	1
#		else /* __cplusplus > 201103L */
#			define MY_HAS_CPP14	0
#		endif /* __cplusplus > 201103L */
#	endif /* _MSVC_LANG */
#endif /* MY_HAS_CPP14 */
 
/*!
	The version of C++ standard that is being used.
	The C++11 standard.
*/
#ifndef MY_HAS_CPP11
#	ifdef _MSVC_LANG
#		if _MSVC_LANG > 199711L
#			define MY_HAS_CPP11	1
#		else /* _MSVC_LANG > 199711L */
#			define MY_HAS_CPP11	0
#		endif /* _MSVC_LANG > 199711L */
#	else /* _MSVC_LANG */
#		if __cplusplus > 199711L
#			define MY_HAS_CPP11	1
#		else /* __cplusplus > 199711L */
#			define MY_HAS_CPP11	0
#		endif /* __cplusplus > 199711L */
#	endif /* _MSVC_LANG */
#endif /* MY_HAS_CPP11 */

extern DWORD TWinVersion; // define in tlib.cpp

#define TLIB_SLEEPTIMER 32000

#define IsNewShell() (LOBYTE(LOWORD(TWinVersion)) >= 4)

#define IsWin31()                                                              \
    (LOBYTE(LOWORD(TWinVersion)) == 3 && HIBYTE(LOWORD(TWinVersion)) < 20)
#define IsWin95()                                                              \
    (LOBYTE(LOWORD(TWinVersion)) >= 4 && TWinVersion >= 0x80000000)

#define IsWinNT350()                                                           \
    (LOBYTE(LOWORD(TWinVersion)) == 3 && TWinVersion < 0x80000000 &&           \
     HIBYTE(LOWORD(TWinVersion)) == 50)
#define IsWinNT() (LOBYTE(LOWORD(TWinVersion)) >= 4 && TWinVersion < 0x80000000)
#define IsWin2K() (LOBYTE(LOWORD(TWinVersion)) >= 5 && TWinVersion < 0x80000000)
#define IsWinXP()                                                              \
    ((LOBYTE(LOWORD(TWinVersion)) >= 6 ||                                      \
      LOBYTE(LOWORD(TWinVersion)) == 5 && HIBYTE(LOWORD(TWinVersion)) >= 1) && \
     TWinVersion < 0x80000000)
#define IsWinVista()                                                           \
    (LOBYTE(LOWORD(TWinVersion)) >= 6 && TWinVersion < 0x80000000)
#define IsWin7()                                                               \
    ((LOBYTE(LOWORD(TWinVersion)) >= 7 ||                                      \
      LOBYTE(LOWORD(TWinVersion)) == 6 && HIBYTE(LOWORD(TWinVersion)) >= 1) && \
     TWinVersion < 0x80000000)
#define IsWin8()                                                               \
    ((LOBYTE(LOWORD(TWinVersion)) >= 7 ||                                      \
      LOBYTE(LOWORD(TWinVersion)) == 6 && HIBYTE(LOWORD(TWinVersion)) >= 2) && \
     TWinVersion < 0x80000000)

#define IsWin81()                                                              \
    ((LOBYTE(LOWORD(TWinVersion)) >= 7 ||                                      \
      LOBYTE(LOWORD(TWinVersion)) == 6 && HIBYTE(LOWORD(TWinVersion)) >= 3) && \
     TWinVersion < 0x80000000)
#define IsWin10()                                                              \
    (LOBYTE(LOWORD(TWinVersion)) >= 10 && TWinVersion < 0x80000000)

struct WINPOS {
    int x;
    int y;
    int cx;
    int cy;
};

enum DlgItemFlags {
    NONE_FIT = 0x000,
    LEFT_FIT = 0x001,
    HMID_FIT = 0x002,
    RIGHT_FIT = 0x004,
    TOP_FIT = 0x008,
    VMID_FIT = 0x010,
    BOTTOM_FIT = 0x020,
    FIT_SKIP = 0x800,
    X_FIT = LEFT_FIT | RIGHT_FIT,
    Y_FIT = TOP_FIT | BOTTOM_FIT,
    XY_FIT = X_FIT | Y_FIT,
};

struct DlgItem {
    DWORD flags; // DlgItemFlags
    HWND hWnd;
    UINT id;
    WINPOS wpos;
};

struct TRect : public RECT {
    TRect(long x = 0, long y = 0, long cx = 0, long cy = 0) {
        Init(x, y, cx, cy);
    }
    TRect(const RECT &rc) { *this = rc; }
    TRect(const POINT &tl, const POINT &br) {
        left = tl.x;
        top = tl.y;
        right = br.x;
        bottom = br.y;
    }
    TRect(const POINTS &tl, const POINTS &br) {
        left = tl.x;
        top = tl.y;
        right = br.x;
        bottom = br.y;
    }
    TRect(const POINT &tl, long cx, long cy) {
        left = tl.x;
        top = tl.y;
        right = left + cx;
        bottom = top + cy;
    }
    TRect(const POINTS &tl, long cx, long cy) {
        left = tl.x;
        top = tl.y;
        right = left + cx;
        bottom = top + cy;
    }

    void Init(long x = 0, long y = 0, long cx = 0, long cy = 0) {
        left = x;
        top = y;
        right = x + cx;
        bottom = y + cy;
    }
    long &x() { return left; }
    long &y() { return top; }
    long cx() const { return right - left; }
    long cy() const { return bottom - top; }
    void set_x(long x) { left = x; }
    void set_y(long y) { top = y; }
    void set_cx(long v) { right = left + v; }
    void set_cy(long v) { bottom = top + v; }
    void Regular() {
        if (left > right) {
            long t = left;
            left = right;
            right = t;
        }
        if (top > bottom) {
            long t = top;
            top = bottom;
            bottom = t;
        }
    }
    void Inflate(long cx, long cy) {
        left -= cx;
        right += cx;
        top -= cy;
        bottom += cy;
    }
    void Size(long cx, long cy) {
        right = left + cx;
        bottom = top + cy;
    }
    BOOL operator==(const TRect &rc) {
        return (left == rc.left) && (top == rc.top) && (right == rc.right) &&
               (bottom == rc.bottom);
    }
    BOOL operator!=(const TRect &rc) { return !(*this == rc); }
};

class TWin {
  protected:
    TRect rect;
    TRect orgRect;
    HACCEL hAccel;
    TWin *parent;
    BOOL sleepBusy; // for TWin::Sleep() only
    BOOL isUnicode;
    BOOL scrollHack; // need 32bit scroll hack, if EventScroll is overridden

  public:
    TWin(TWin *_parent = NULL);
    virtual ~TWin();

    HWND hWnd;
    DWORD modalCount;
    DWORD twinId;

    virtual BOOL Create(HINSTANCE hI = NULL);
    virtual void Destroy(void);
    virtual void Show();
    virtual void Hide();

    virtual LRESULT WinProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
    virtual LRESULT DefWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
    virtual BOOL PreProcMsg(MSG *msg);

    virtual BOOL EvCommand(WORD wNotifyCode, WORD wID, LPARAM hwndCtl);
    virtual BOOL EvSysCommand(WPARAM uCmdType, POINTS pos);
    virtual BOOL EvCreate(LPARAM lParam);
    virtual BOOL EvClose(void);
    virtual BOOL EvDestroy(void);
    virtual BOOL EvNcDestroy(void);
    virtual BOOL EvQueryEndSession(BOOL nSession, BOOL nLogOut);
    virtual BOOL EvEndSession(BOOL nSession, BOOL nLogOut);
    virtual BOOL EvQueryOpen(void);
    virtual BOOL EvPaint(void);
    virtual BOOL EvNcPaint(HRGN hRgn);
    virtual BOOL EvSize(UINT fwSizeType, WORD nWidth, WORD nHeight);
    virtual BOOL EvMove(int xpos, int ypos);
    virtual BOOL EvShowWindow(BOOL fShow, int fnStatus);
    virtual BOOL EvGetMinMaxInfo(MINMAXINFO *info);
    virtual BOOL EvTimer(WPARAM timerID, TIMERPROC proc);
    virtual BOOL EvSetCursor(HWND cursorWnd, WORD nHitTest, WORD wMouseMsg);
    virtual BOOL EvMouseMove(UINT fwKeys, POINTS pos);
    virtual BOOL EvNcHitTest(POINTS pos, LRESULT *result);
    virtual BOOL EvMeasureItem(UINT ctlID, MEASUREITEMSTRUCT *lpMis);
    virtual BOOL EvDrawItem(UINT ctlID, DRAWITEMSTRUCT *lpDis);
    virtual BOOL EvMenuSelect(UINT uItem, UINT fuFlag, HMENU hMenu);
    virtual BOOL EvDropFiles(HDROP hDrop);
    virtual BOOL EvNotify(UINT ctlID, NMHDR *pNmHdr);
    virtual BOOL EvContextMenu(HWND childWnd, POINTS pos);
    virtual BOOL EvHotKey(int hotKey);
    virtual BOOL EvActivateApp(BOOL fActivate, DWORD dwThreadID);
    virtual BOOL EvActivate(BOOL fActivate, DWORD fMinimized, HWND hActiveWnd);
    virtual BOOL EvChar(WCHAR code, LPARAM keyData);
    virtual BOOL EvWindowPosChanged(WINDOWPOS *pos);
    virtual BOOL EvWindowPosChanging(WINDOWPOS *pos);

    virtual BOOL EventButton(UINT uMsg, int nHitTest, POINTS pos);
    virtual BOOL EventKey(UINT uMsg, int nVirtKey, LONG lKeyData);
    virtual BOOL EventMenuLoop(UINT uMsg, BOOL fIsTrackPopupMenu);
    virtual BOOL EventInitMenu(UINT uMsg, HMENU hMenu, UINT uPos,
                               BOOL fSystemMenu);
    virtual BOOL EventCtlColor(UINT uMsg, HDC hDcCtl, HWND hWndCtl,
                               HBRUSH *result);
    virtual BOOL EventFocus(UINT uMsg, HWND focusWnd);
    virtual BOOL EventScrollWrapper(UINT uMsg, int nScrollCode, int nPos,
                                    HWND hScroll);
    virtual BOOL EventScroll(UINT uMsg, int nScrollCode, int nPos,
                             HWND hScroll);

    virtual BOOL EventApp(UINT uMsg, WPARAM wParam, LPARAM lParam);
    virtual BOOL EventUser(UINT uMsg, WPARAM wParam, LPARAM lParam);
    virtual BOOL EventSystem(UINT uMsg, WPARAM wParam, LPARAM lParam);

    virtual UINT GetDlgItemText(int ctlId, LPSTR buf, int len);
    virtual UINT GetDlgItemTextW(int ctlId, WCHAR *buf, int len);
    // virtual UINT GetDlgItemTextU8(int ctlId, char *buf, int len);

    virtual BOOL SetDlgItemText(int ctlId, LPCSTR buf);
    virtual BOOL SetDlgItemTextW(int ctlId, const WCHAR *buf);
    // virtual BOOL SetDlgItemTextU8(int ctlId, const char *buf);

    virtual int GetDlgItemInt(int ctlId, BOOL *err = NULL, BOOL sign = TRUE);
    virtual BOOL SetDlgItemInt(int ctlId, int val, BOOL sign = TRUE);
    virtual HWND GetDlgItem(int ctlId);
    virtual BOOL CheckDlgButton(int ctlId, UINT check);
    virtual UINT IsDlgButtonChecked(int ctlId);
    virtual BOOL IsWindowVisible(void);
    virtual BOOL EnableWindow(BOOL is_enable);

    virtual int MessageBox(LPCSTR msg, LPCSTR title = "msg",
                           UINT style = MB_OK);
    virtual int MessageBoxW(LPCWSTR msg, LPCWSTR title = L"msg",
                            UINT style = MB_OK);
    // virtual int MessageBoxU8(LPCSTR msg, LPCSTR title = "msg",
    //                         UINT style = MB_OK);

    virtual BOOL BringWindowToTop(void);
    virtual BOOL SetForegroundWindow(void);
    virtual BOOL SetForceForegroundWindow(void);
    virtual BOOL ShowWindow(int mode);
    virtual BOOL PostMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
    virtual BOOL PostMessageW(UINT uMsg, WPARAM wParam, LPARAM lParam);
    virtual LRESULT SendMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
    virtual LRESULT SendMessageW(UINT uMsg, WPARAM wParam, LPARAM lParam);
    virtual LRESULT SendDlgItemMessage(int ctlId, UINT uMsg, WPARAM wParam,
                                       LPARAM lParam);
    virtual LRESULT SendDlgItemMessageW(int ctlId, UINT uMsg, WPARAM wParam,
                                        LPARAM lParam);
    virtual BOOL GetWindowRect(RECT *_rect = NULL);
    virtual BOOL GetClientRect(RECT *rc);
    virtual BOOL SetWindowPos(HWND hInsAfter, int x, int y, int cx, int cy,
                              UINT fuFlags);
    virtual HWND SetActiveWindow(void);

    virtual int GetWindowText(LPSTR text, int size);
    virtual BOOL GetWindowTextW(WCHAR *text, int size);
    // virtual BOOL GetWindowTextU8(char *text, int size);

    virtual BOOL SetWindowText(LPCSTR text);
    virtual BOOL SetWindowTextW(const WCHAR *text);
    // virtual BOOL SetWindowTextU8(const char *text);

    virtual int GetWindowTextLengthW(void);
    // virtual int GetWindowTextLengthU8(void);

    virtual LONG_PTR GetWindowLong(int index);
    virtual LONG_PTR SetWindowLong(int index, LONG_PTR val);

    virtual WORD GetWindowWord(int index);
    virtual WORD SetWindowWord(int index, WORD val);

    virtual BOOL InvalidateRect(const RECT *rc, BOOL fErase);

    virtual BOOL MoveWindow(int x, int y, int cx, int cy, int bRepaint);
    virtual BOOL FitMoveWindow(int x, int y);

    virtual BOOL Sleep(UINT mSec);
    virtual BOOL Idle(void);

    virtual TWin *GetParent(void) { return parent; };
    virtual void SetParent(TWin *_parent) { parent = _parent; };
    virtual TWin *Parent() { return parent; }
};

class TDlg : public TWin {
  protected:
    UINT resId;
    BOOL modalFlg;
    int maxItems;
    DlgItem *dlgItems;

  public:
    TDlg(UINT resid = 0, TWin *_parent = NULL);
    virtual ~TDlg();

    virtual BOOL Create(HINSTANCE hI = NULL) override;
    virtual void Destroy(void) override;
    virtual int Exec(void);
    virtual void EndDialog(int);
    UINT ResId(void) { return resId; }
    virtual int SetDlgItem(UINT ctl_id, DWORD flags = 0);
    virtual BOOL FitDlgItems();

    virtual BOOL EvCreate(LPARAM lParam) override;
    virtual BOOL EvCommand(WORD wNotifyCode, WORD wID, LPARAM hwndCtl) override;
    virtual BOOL EvSysCommand(WPARAM uCmdType, POINTS pos) override;
    virtual BOOL EvQueryOpen(void) override;

    virtual BOOL PreProcMsg(MSG *msg) override;
    virtual LRESULT WinProc(UINT uMsg, WPARAM wParam, LPARAM lParam) override;
};

class TApp {
  protected:
    static TApp *tapp;

    std::map<HWND, TWin *> hash;

    TWin *mainWnd;
    TWin *preWnd;
    HINSTANCE hI;
    DWORD twinId;

    virtual BOOL InitApp(void);

  public:
    TApp(HINSTANCE _hI);
    virtual void InitWindow() = 0;
    virtual ~TApp();
    virtual int Run();
    virtual BOOL PreProcMsg(MSG *msg);

    void AddWin(TWin *win) { preWnd = win; }

    void AddWinByWnd(TWin *win, HWND hWnd) {
        win->hWnd = hWnd;
        hash.insert(std::pair<HWND, TWin *>(hWnd, win));
    }

    void DelWin(TWin *win) {
        for (auto item : hash) {
            if (item.second == win) {
                hash.erase(item.first);
                continue;
            }
        }
    }

    TWin *SearchWnd(HWND hWnd) {
        std::map<HWND, TWin *>::iterator l_it;
        l_it = hash.find(hWnd);
        if (l_it != hash.end()) {
            return hash[hWnd];
        } else {
            return nullptr;
        }
    }

    static TApp *GetApp() { return tapp; }
    static void Idle(DWORD timeout = 0);
    static HINSTANCE GetInstance() { return tapp->hI; }
    static LRESULT CALLBACK WinProc(HWND hW, UINT uMsg, WPARAM wParam,
                                    LPARAM lParam);
    static DWORD GenTWinID() { return tapp ? tapp->twinId++ : 0; }
};

#endif
